【React19】サクッと理解するuseOptimistic
こんにちは、戸田です。
React19 からuseOptimistic
という新しい hooks が登場しました。
今後使われることがある hooks だと思うのでまとめてみました。
以下が useOptimistic の公式リファレンスです。
useOptimistic とは
簡単な説明
useOptimistic
は、ある非同期アクションが進行中の間だけ、楽観的更新をするための React フックです。具体的には、以下のようなシナリオで活用されます:
- フォームの送信:ユーザーがフォームを送信した際、サーバーのレスポンスを待たずに入力内容を即座に UI に反映させる。
- データの更新:ユーザーがデータを編集した際、変更を即座に反映させ、サーバーへの保存が完了した後に最終的なデータを確定する。
楽観的更新とは
useOptimistic を使う際における楽観的とは、API のデータ取得で想定する結果が返ってくるものとして考えることです。
白いボタンを押したら(本当はサーバーからレスポンスを待つ必要があるけど最終的に)赤色が変化するだろうから先に変更にしちゃえ!みたいな感じです
useOptimistic
を使用して定義される state を楽観的 stateといいます
メリット
useOptimistic
を使用すると実際の結果よりも先に楽観的な結果を即座に返却するため、ユーザー体験が非常に良くなります。
useOptimistic の 使い方
const [optimisticState, addOptimistic] = useOptimistic(state, updateFn);
引数
state
: 初期状態や、実行中のアクションが存在しない場合に返される値。updateFn(currentState, optimisticValue)
:currentState
: 現在のステート値。optimisticValue
:addOptimistic
に渡された楽観的更新に使用する値。- この関数は、
currentState
とoptimisticValue
を元に新しい楽観的ステートを返します。
返り値
optimisticState
: 結果としての楽観的ステート。非同期アクションがない場合はstate
と同一になり、アクションが実行中の場合はupdateFn
が返す値となります。addOptimistic
: 楽観的な更新を行うためのディスパッチ関数。任意の型の引数optimisticValue
を 1 つ受け取り、updateFn
を通じてステートが更新されます。
使用例: いいねボタンの実装
具体的な使用例を通じて、useOptimistic
を説明します。
以下は X(旧 Twitter)でいいね ❤️ をする際の楽観的更新の例です。
import { startTransition, useOptimistic, useState } from "react";
const sendLikeAction = async (): Promise<string> => {
try {
// ダミー送信(3秒 delay)
await new Promise((resolve) => setTimeout(resolve, 3000));
return "red";
} catch (error) {
console.error(error);
return "white";
}
};
const optimisticAction = (_currentState: string, optimisticColor: string) => {
// ここで楽観的な処理をする
return optimisticColor;
};
export const LikeButton = () => {
const [color, setColor] = useState<string>("white");
const [displayLikeColor, addOptimistic] = useOptimistic<string, string>(color, optimisticAction);
const handleLikeClick = () => {
// 非同期アクションが進行中と示すためにstartTransitionを使用している(useActionStateやuseTransition内でも使用可能)
startTransition(async () => {
// 楽観的に表示する際の色を指定(yellowになる)
addOptimistic("yellow");
// データを取得してから元のstateを更新
const sendedIsLike = await sendLikeAction();
setColor(sendedIsLike);
});
// transitionが終わったので楽観的な表示を解除(redになる)
};
return (
<div>
<h3>誰かの投稿</h3>
<div>
<span onClick={handleLikeClick} style={{ color: displayLikeColor }}>
♡
</span>
</div>
</div>
);
};
コメントなし
import { startTransition, useOptimistic, useState } from "react";
const sendLikeAction = async (): Promise<string> => {
try {
await new Promise((resolve) => setTimeout(resolve, 3000));
return "red";
} catch (error) {
console.error(error);
return "white";
}
};
const optimisticAction = (_currentState: string, optimisticColor: string) => {
return optimisticColor;
};
export const LikeButton = () => {
const [color, setColor] = useState<string>("white");
const [displayLikeColor, addOptimistic] = useOptimistic<string, string>(color, optimisticAction);
const handleLikeClick = () => {
startTransition(async () => {
addOptimistic("yellow");
const sendedIsLike = await sendLikeAction();
setColor(sendedIsLike);
});
};
return (
<div>
<h3>誰かの投稿</h3>
<div>
<span onClick={handleLikeClick} style={{ color: displayLikeColor }}>
♡
</span>
</div>
</div>
);
};
コードの解説
今回の例では、useOptimistic
フックを使って「いいね」ボタンの色を楽観的に更新し、ユーザーに迅速なフィードバックを提供しています。以下に、コードの各部分とその動作について簡単に説明します。
動作確認動画
この動画では分かりやすくするために表示を少し変えています。
流れ
useState
で初期のハートの色を"white"
にするuseOptimistic
に 初期値となる color と、楽観的更新を行うoptimisticAction
を渡す- いいね(🤍)がクリックされる
addOptimistic
の引数に"yellow"
を渡して、楽観的更新をする(ハートが黄色になる 💛)- ダミー送信をする(3 秒 delay)
- 送信が終わった後、返却された値("red"❤️ or "white"🤍)の色に UI を更新する
startTransition を使用した理由
「useOptimistic とは」の中で以下のように説明しました。
useOptimistic はある非同期アクションが進行中の間だけ楽観的更新をするための React フックです。
startTransition の中で行われたステート更新はトランジションであると見なされます。
トランジションとしてマークされた state 更新は、他の state 更新によって中断されます。
なので3秒後に useState で更新した state(color)が displayLikeColor より優先度が高いため、color が更新された時点で displayLikeColor も更新されました。
このようにトランジション状態にするためにstartTransition
を使用しました。
また、startTransition を使わなくてもuseActionState
やuseTransition
を使うことでもトランジション状態を作れます。
// useActionState
const fn = () => {
// トランジション状態
};
const [state, formAction] = useActionState(fn, initialState, permalink);
// useTransition
const [isPending, startTransition] = useTransition();
startTransition(() => {
// トランジション状態
});
より詳しく知りたい方はこちらの本がおすすめです。
エラーハンドリングの重要性
楽観的な UI 更新では、サーバーからのレスポンスを待たずに UI を即座に更新するため、エラーが発生した場合の対処が重要です。今回の例では、エラーが発生した場合にボタンの色を白に戻し、コンソールにエラーメッセージを表示しています。実際のアプリケーションでは、ユーザーにエラーメッセージを表示するなど、さらに慎重なエラーハンドリングが求められます。
まとめ
useOptimistic
フックを活用することで、非同期アクションが完了する前に UI を即座に更新し、ユーザーにスムーズな操作感を提供できます。この手法は、「いいね」ボタンだけでなく、フォームの送信やデータの編集など、さまざまなシナリオで有効に活用できます。ぜひ活用してみてください。
最後に、前回「サクッと理解する useActionState」という記事も書いたのでぜひ見てください。
参考リンク